@@ -34,10 +34,67 @@ class @Utils |
||
34 | 34 |
body?(modal.querySelector('.modal-body')) |
35 | 35 |
$(modal).modal('show') |
36 | 36 |
|
37 |
- @handleDryRunButton: (button, data = $(button.form).serialize()) -> |
|
37 |
+ @handleDryRunButton: (button, data = if button.form then $(':input[name!="_method"]', button.form).serialize() else '') -> |
|
38 | 38 |
$(button).prop('disabled', true) |
39 |
+ cleanup = -> $(button).prop('disabled', false) |
|
40 |
+ |
|
41 |
+ url = $(button).data('action-url') |
|
42 |
+ with_event_mode = $(button).data('with-event-mode') |
|
43 |
+ |
|
44 |
+ if with_event_mode is 'no' |
|
45 |
+ return @invokeDryRun(url, data, cleanup) |
|
46 |
+ |
|
47 |
+ Utils.showDynamicModal """ |
|
48 |
+ <h5>Event to send#{if with_event_mode is 'maybe' then ' (Optional)' else ''}</h5> |
|
49 |
+ <form class="dry-run-form" method="post"> |
|
50 |
+ <div class="form-group"> |
|
51 |
+ <textarea rows="10" name="event" class="payload-editor" data-height="200"> |
|
52 |
+ {} |
|
53 |
+ </textarea> |
|
54 |
+ </div> |
|
55 |
+ <div class="form-group"> |
|
56 |
+ <input value="Dry Run" class="btn btn-primary" type="submit" /> |
|
57 |
+ </div> |
|
58 |
+ </form> |
|
59 |
+ """, |
|
60 |
+ body: (body) => |
|
61 |
+ form = $(body).find('.dry-run-form') |
|
62 |
+ payload_editor = form.find('.payload-editor') |
|
63 |
+ if previous = $(button).data('payload') |
|
64 |
+ payload_editor.text(previous) |
|
65 |
+ window.setupJsonEditor(payload_editor) |
|
66 |
+ form.submit (e) => |
|
67 |
+ e.preventDefault() |
|
68 |
+ json = $(e.target).find('.payload-editor').val() |
|
69 |
+ json = '{}' if json == '' |
|
70 |
+ try |
|
71 |
+ payload = JSON.parse(json) |
|
72 |
+ throw true unless payload.constructor is Object |
|
73 |
+ if Object.keys(payload).length == 0 |
|
74 |
+ json = '' |
|
75 |
+ else |
|
76 |
+ json = JSON.stringify(payload) |
|
77 |
+ catch |
|
78 |
+ alert 'Invalid JSON object.' |
|
79 |
+ return |
|
80 |
+ if json == '' |
|
81 |
+ if with_event_mode is 'yes' |
|
82 |
+ alert 'Event is required for this agent to run.' |
|
83 |
+ return |
|
84 |
+ dry_run_data = data |
|
85 |
+ $(button).data('payload', null) |
|
86 |
+ else |
|
87 |
+ dry_run_data = "event=#{encodeURIComponent(json)}&#{data}" |
|
88 |
+ $(button).data('payload', json) |
|
89 |
+ $(body).closest('[role=dialog]').on 'hidden.bs.modal', => |
|
90 |
+ @invokeDryRun(url, dry_run_data, cleanup) |
|
91 |
+ .modal('hide') |
|
92 |
+ title: 'Dry Run' |
|
93 |
+ onHide: cleanup |
|
94 |
+ |
|
95 |
+ @invokeDryRun: (url, data, callback) -> |
|
39 | 96 |
$('body').css(cursor: 'progress') |
40 |
- $.ajax type: 'POST', url: $(button).data('action-url'), dataType: 'json', data: data |
|
97 |
+ $.ajax type: 'POST', url: url, dataType: 'json', data: data |
|
41 | 98 |
.always => |
42 | 99 |
$('body').css(cursor: 'auto') |
43 | 100 |
.done (json) => |
@@ -55,7 +112,7 @@ class @Utils |
||
55 | 112 |
find('.agent-dry-run-events').text(json.events).end(). |
56 | 113 |
find('.agent-dry-run-memory').text(json.memory) |
57 | 114 |
title: 'Dry Run Results', |
58 |
- onHide: -> $(button).prop('disabled', false) |
|
115 |
+ onHide: callback |
|
59 | 116 |
.fail (xhr, status, error) -> |
60 | 117 |
alert('Error: ' + error) |
61 |
- $(button).prop('disabled', false) |
|
118 |
+ callback() |
@@ -1,7 +1,7 @@ |
||
1 | 1 |
module DryRunnable |
2 | 2 |
extend ActiveSupport::Concern |
3 | 3 |
|
4 |
- def dry_run! |
|
4 |
+ def dry_run!(event = nil) |
|
5 | 5 |
@dry_run = true |
6 | 6 |
|
7 | 7 |
log = StringIO.new |
@@ -13,7 +13,12 @@ module DryRunnable |
||
13 | 13 |
begin |
14 | 14 |
raise "#{short_type} does not support dry-run" unless can_dry_run? |
15 | 15 |
readonly! |
16 |
- check |
|
16 |
+ if event |
|
17 |
+ raise "This agent cannot receive an event!" unless can_receive_events? |
|
18 |
+ receive([event]) |
|
19 |
+ else |
|
20 |
+ check |
|
21 |
+ end |
|
17 | 22 |
rescue => e |
18 | 23 |
error "Exception during dry-run. #{e.message}: #{e.backtrace.join("\n")}" |
19 | 24 |
end |
@@ -37,7 +37,7 @@ class AgentsController < ApplicationController |
||
37 | 37 |
def dry_run |
38 | 38 |
attrs = params[:agent] || {} |
39 | 39 |
if agent = current_user.agents.find_by(id: params[:id]) |
40 |
- # PUT /agents/:id/dry_run |
|
40 |
+ # POST /agents/:id/dry_run |
|
41 | 41 |
if attrs.present? |
42 | 42 |
type = agent.type |
43 | 43 |
agent = Agent.build_for_type(type, current_user, attrs) |
@@ -50,7 +50,13 @@ class AgentsController < ApplicationController |
||
50 | 50 |
agent.name ||= '(Untitled)' |
51 | 51 |
|
52 | 52 |
if agent.valid? |
53 |
- results = agent.dry_run! |
|
53 |
+ if event_payload = params[:event] |
|
54 |
+ dummy_agent = Agent.build_for_type('ManualEventAgent', current_user, name: 'Dry-Runner') |
|
55 |
+ dummy_agent.readonly! |
|
56 |
+ event = dummy_agent.events.build(user: current_user, payload: event_payload) |
|
57 |
+ end |
|
58 |
+ |
|
59 |
+ results = agent.dry_run!(event) |
|
54 | 60 |
|
55 | 61 |
render json: { |
56 | 62 |
log: results[:log], |
@@ -37,4 +37,16 @@ module AgentHelper |
||
37 | 37 |
}.join(delimiter).html_safe |
38 | 38 |
end |
39 | 39 |
end |
40 |
+ |
|
41 |
+ def agent_dry_run_with_event_mode(agent) |
|
42 |
+ case |
|
43 |
+ when agent.cannot_receive_events? |
|
44 |
+ 'no'.freeze |
|
45 |
+ when agent.cannot_be_scheduled? |
|
46 |
+ # incoming event is the only trigger for the agent |
|
47 |
+ 'yes'.freeze |
|
48 |
+ else |
|
49 |
+ 'maybe'.freeze |
|
50 |
+ end |
|
51 |
+ end |
|
40 | 52 |
end |
@@ -7,7 +7,7 @@ |
||
7 | 7 |
|
8 | 8 |
<% if agent.can_dry_run? %> |
9 | 9 |
<li> |
10 |
- <%= link_to icon_tag('glyphicon-refresh') + ' Dry Run', '#', 'data-action-url' => dry_run_agent_path(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this, '_method=PUT')" %> |
|
10 |
+ <%= link_to icon_tag('glyphicon-refresh') + ' Dry Run', '#', 'data-action-url' => dry_run_agent_path(agent), 'data-with-event-mode' => agent_dry_run_with_event_mode(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this)" %> |
|
11 | 11 |
</li> |
12 | 12 |
<% end %> |
13 | 13 |
|
@@ -25,6 +25,6 @@ |
||
25 | 25 |
<div class="form-group"> |
26 | 26 |
<%= submit_tag "Save", :class => "btn btn-primary" %> |
27 | 27 |
<% if agent.can_dry_run? %> |
28 |
- <%= button_tag class: 'btn btn-default agent-dry-run-button', type: 'button', 'data-action-url' => agent.persisted? ? dry_run_agent_path(agent) : dry_run_agents_path do %><%= icon_tag('glyphicon-refresh') %> Dry Run<% end %> |
|
28 |
+ <%= button_tag class: 'btn btn-default agent-dry-run-button', type: 'button', 'data-action-url' => agent.persisted? ? dry_run_agent_path(agent) : dry_run_agents_path, 'data-with-event-mode' => agent_dry_run_with_event_mode(agent) do %><%= icon_tag('glyphicon-refresh') %> Dry Run<% end %> |
|
29 | 29 |
<% end %> |
30 | 30 |
</div> |
@@ -2,7 +2,7 @@ Huginn::Application.routes.draw do |
||
2 | 2 |
resources :agents do |
3 | 3 |
member do |
4 | 4 |
post :run |
5 |
- put :dry_run |
|
5 |
+ post :dry_run |
|
6 | 6 |
post :handle_details_post |
7 | 7 |
put :leave_scenario |
8 | 8 |
delete :remove_events |
@@ -7,10 +7,22 @@ describe DryRunnable do |
||
7 | 7 |
can_dry_run! |
8 | 8 |
|
9 | 9 |
def check |
10 |
+ perform |
|
11 |
+ end |
|
12 |
+ |
|
13 |
+ def receive(events) |
|
14 |
+ events.each do |event| |
|
15 |
+ perform(event.payload['prefix']) |
|
16 |
+ end |
|
17 |
+ end |
|
18 |
+ |
|
19 |
+ private |
|
20 |
+ |
|
21 |
+ def perform(prefix = nil) |
|
10 | 22 |
log "Logging" |
11 |
- create_event payload: { 'test' => 'foo' } |
|
23 |
+ create_event payload: { 'test' => "#{prefix}foo" } |
|
12 | 24 |
error "Recording error" |
13 |
- create_event payload: { 'test' => 'bar' } |
|
25 |
+ create_event payload: { 'test' => "#{prefix}bar" } |
|
14 | 26 |
self.memory = { 'last_status' => 'ok', 'dry_run' => dry_run? } |
15 | 27 |
save! |
16 | 28 |
end |
@@ -46,21 +58,6 @@ describe DryRunnable do |
||
46 | 58 |
expect(messages).to eq(['Logging', 'Recording error']) |
47 | 59 |
end |
48 | 60 |
|
49 |
- it "traps logging, event emission and memory updating, with dry_run? returning true" do |
|
50 |
- results = nil |
|
51 |
- |
|
52 |
- expect { |
|
53 |
- results = @agent.dry_run! |
|
54 |
- @agent.reload |
|
55 |
- }.not_to change { |
|
56 |
- [@agent.memory, counts] |
|
57 |
- } |
|
58 |
- |
|
59 |
- expect(results[:log]).to match(/\AI, .+ INFO -- : Logging\nE, .+ ERROR -- : Recording error\n/) |
|
60 |
- expect(results[:events]).to eq([{ 'test' => 'foo' }, { 'test' => 'bar' }]) |
|
61 |
- expect(results[:memory]).to eq({ 'last_status' => 'ok', 'dry_run' => true }) |
|
62 |
- end |
|
63 |
- |
|
64 | 61 |
it "does not perform dry-run if Agent does not support dry-run" do |
65 | 62 |
stub(@agent).can_dry_run? { false } |
66 | 63 |
|
@@ -77,4 +74,36 @@ describe DryRunnable do |
||
77 | 74 |
expect(results[:events]).to eq([]) |
78 | 75 |
expect(results[:memory]).to eq({}) |
79 | 76 |
end |
77 |
+ |
|
78 |
+ describe "dry_run!" do |
|
79 |
+ it "traps any destructive operations during a run" do |
|
80 |
+ results = nil |
|
81 |
+ |
|
82 |
+ expect { |
|
83 |
+ results = @agent.dry_run! |
|
84 |
+ @agent.reload |
|
85 |
+ }.not_to change { |
|
86 |
+ [@agent.memory, counts] |
|
87 |
+ } |
|
88 |
+ |
|
89 |
+ expect(results[:log]).to match(/\AI, .+ INFO -- : Logging\nE, .+ ERROR -- : Recording error\n/) |
|
90 |
+ expect(results[:events]).to eq([{ 'test' => 'foo' }, { 'test' => 'bar' }]) |
|
91 |
+ expect(results[:memory]).to eq({ 'last_status' => 'ok', 'dry_run' => true }) |
|
92 |
+ end |
|
93 |
+ |
|
94 |
+ it "traps any destructive operations during a run when an event is given" do |
|
95 |
+ results = nil |
|
96 |
+ |
|
97 |
+ expect { |
|
98 |
+ results = @agent.dry_run!(Event.new(payload: { 'prefix' => 'super' })) |
|
99 |
+ @agent.reload |
|
100 |
+ }.not_to change { |
|
101 |
+ [@agent.memory, counts] |
|
102 |
+ } |
|
103 |
+ |
|
104 |
+ expect(results[:log]).to match(/\AI, .+ INFO -- : Logging\nE, .+ ERROR -- : Recording error\n/) |
|
105 |
+ expect(results[:events]).to eq([{ 'test' => 'superfoo' }, { 'test' => 'superbar' }]) |
|
106 |
+ expect(results[:memory]).to eq({ 'last_status' => 'ok', 'dry_run' => true }) |
|
107 |
+ end |
|
108 |
+ end |
|
80 | 109 |
end |
@@ -377,6 +377,19 @@ describe AgentsController do |
||
377 | 377 |
[users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at] |
378 | 378 |
} |
379 | 379 |
end |
380 |
+ |
|
381 |
+ it "accepts an event" do |
|
382 |
+ sign_in users(:bob) |
|
383 |
+ agent = agents(:bob_website_agent) |
|
384 |
+ url_from_event = "http://xkcd.com/?from_event=1".freeze |
|
385 |
+ expect { |
|
386 |
+ post :dry_run, id: agent, event: { url: url_from_event } |
|
387 |
+ }.not_to change { |
|
388 |
+ [users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at] |
|
389 |
+ } |
|
390 |
+ json = JSON.parse(response.body) |
|
391 |
+ expect(json['log']).to match(/^I, .* : Fetching #{Regexp.quote(url_from_event)}$/) |
|
392 |
+ end |
|
380 | 393 |
end |
381 | 394 |
|
382 | 395 |
describe "DELETE memory" do |